﻿/*
 * Copyright 2010 Pieter De Rycke
 *
 * This file is part of Frozen Bubble XNA.
 *
 * Frozen Bubble XNA is free software: you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * version 2, as published by the Free Software Foundation.
 *
 * Frozen Bubble XNA is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with Frozen Bubble XNA. If not, see 
 * <http://www.gnu.org/licenses/>.
 *
 * Artwork:
 *    Alexis Younes <73lab at free.fr>
 *      (everything but the bubbles)
 *    Amaury Amblard-Ladurantie <amaury at linuxfr.org>
 *      (the bubbles)
 *
 * Soundtrack:
 *    Matthias Le Bidan <matthias.le_bidan at caramail.com>
 *      (the three musics and all the sound effects)
 *
 * Design & Programming:
 *    Guillaume Cottenceau <guillaume.cottenceau at free.fr>
 *      (design and manage the project, whole Perl sourcecode)
 *
 * XNA and Windows Phone 7 port:
 *    Pieter De Rycke
 *
 * Websites:
 *    http://www.frozen-bubble.org/
 *    http://pieterderycke.wordpress.com/
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.IO;
using System.IO.IsolatedStorage;

namespace FrozenBubble
{
    public class ScreenManager : DrawableGameComponent
    {
        private List<GameScreen> _screens = new List<GameScreen>();
        private List<GameScreen> _screensToUpdate = new List<GameScreen>();

        private bool _isInitialized;
        private SpriteBatch _spriteBatch;

        private Rectangle _viewportRect;
        private Storage _storage;
        private ContentCache _cache;
        private Configuration _configuration;
        private IInputController _input;

        /// <summary>
        /// Constructs a new screen manager component.
        /// </summary>
        public ScreenManager(FrozenGame game, GraphicsDeviceManager graphics, Storage storage, ContentCache cache)
            : base(game)
        {
            this.GraphicsDeviceManager = graphics;

            _configuration = new Configuration();

            _storage = storage;
            _cache = cache;

#if WINDOWS_PHONE
            _input = new GestureController(game);
            //_input = new TouchController(game);
            game.Components.Add((IGameComponent)_input);
#else
            _input = new KeyboardController(game);
            game.Components.Add((IGameComponent)_input);
#endif
        }

        protected override void LoadContent()
        {
            _spriteBatch = new SpriteBatch(GraphicsDevice);

            _viewportRect = new Rectangle(0, 0,
                GraphicsDevice.Viewport.Width,
                GraphicsDevice.Viewport.Height);

            // Tell each of the screens to load their content.
            List<GameScreen> screens = new List<GameScreen>(_screens); // We do this because tombstoning can touch the list while loading
            foreach (GameScreen screen in screens)
            {
                screen.SpriteBatch = _spriteBatch;  // if we are tombstoning the sprite batch was null when the screen was added
                screen.LoadContent();

                foreach (IScreenComponent component in screen.Components)
                    component.LoadContent();
            }
        }

        /// <summary>
        /// Initializes the screen manager component.
        /// </summary>
        public override void Initialize()
        {
            base.Initialize();

            _isInitialized = true;
        }

        /// <summary>
        /// Tells each screen to draw itself.
        /// </summary>
        public override void Draw(GameTime gameTime)
        {
#if WINDOWS_PHONE
            _spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.AlphaBlend);
#else
            _spriteBatch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
#endif

            foreach (GameScreen screen in _screens)
            {
                if (!screen.IsActive)
                    continue;
                screen.Draw(gameTime);
            }

            _spriteBatch.End();
        }

        public override void Update(GameTime gameTime)
        {
            // Make a copy of the master screen list, to avoid confusion if
            // the process of updating one screen adds or removes others.
            _screensToUpdate.Clear();

            foreach (GameScreen screen in _screens)
                _screensToUpdate.Add(screen);

            bool otherScreenHasFocus = !Game.IsActive;

            while(_screensToUpdate.Count > 0)
            {
                GameScreen screen = _screensToUpdate[_screensToUpdate.Count - 1];
                _screensToUpdate.RemoveAt(_screensToUpdate.Count - 1);

                if (!screen.IsActive)
                    continue;

                if (!otherScreenHasFocus)
                    screen.HandleInput(gameTime);

                screen.Update(gameTime, otherScreenHasFocus, false);

                if (!screen.IsPopup)
                    otherScreenHasFocus = true;
                
                // Update Components
                foreach (IScreenComponent component in screen.Components)
                    component.Update(gameTime);
            }
        }

        public SpriteBatch SpriteBatch 
        {
            get
            {
                return _spriteBatch;
            }
        }

        public Rectangle ViewportRectangle
        {
            get
            {
                return _viewportRect;
            }
        }

        public IEnumerable<GameScreen> Screens
        {
            get
            {
                return new List<GameScreen>(_screens);
            }
        }

        public Storage Storage
        {
            get
            {
                return _storage;
            }
        }

        public Configuration Configuration
        {
            get
            {
                return _configuration;
            }
        }

        public HighScoreManager HighScoreManager
        {
            get
            {
                return _storage.HighScores;
            }
        }

        public GraphicsDeviceManager GraphicsDeviceManager { get; private set; }

        //public void AddScreen(GameScreen screen)
        //{
        //    AddScreen(screen, true);
        //}

        /// <summary>
        /// Adds a new screen to the screen manager.
        /// </summary>
        public void AddScreen(GameScreen screen)
        {
            screen.ScreenManager = this;
            screen.Game = Game;
            screen.SpriteBatch = SpriteBatch;
            screen.Input = _input;
            screen.Storage = _storage;
            screen.Cache = _cache;
            screen.Configuration = _configuration;

            // If we have a graphics device, tell the screen to load content.
            if (_isInitialized)
            {
                screen.LoadContent();

                foreach (IScreenComponent component in screen.Components)
                {
                    component.LoadContent();
                }
            }

            _screens.Add(screen);
        }

        public void RemoveScreen(GameScreen screen)
        {
            // If we have a graphics device, tell the screen to load content.
            if (_isInitialized)
            {
                screen.UnloadContent();
            }

            _screens.Remove(screen);
            _screensToUpdate.Remove(screen);
        }


        public new FrozenGame Game
        {
            get
            {
                return base.Game as FrozenGame;
            }
        }

        /// <summary>
        /// Informs the screen manager to serialize its state to disk.
        /// </summary>
        public void SerializeState()
        {
            // open up isolated storage
            using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                // if our screen manager directory already exists, delete the contents
                if (storage.DirectoryExists("ScreenManager"))
                {
                    DeleteState(storage);
                }

                // otherwise just create the directory
                else
                {
                    storage.CreateDirectory("ScreenManager");
                }

                // create a file we'll use to store the list of screens in the stack
                using (IsolatedStorageFileStream stream = storage.CreateFile("ScreenManager\\ScreenList.dat"))
                {
                    using (BinaryWriter writer = new BinaryWriter(stream))
                    {
                        // write out the full name of all the types in our stack so we can
                        // recreate them if needed.
                        foreach (GameScreen screen in _screens)
                        {
                            if (screen.IsSerializable)
                            {
                                writer.Write(screen.GetType().AssemblyQualifiedName);
                            }
                        }
                    }
                }

                // now we create a new file stream for each screen so it can save its state
                // if it needs to. we name each file "ScreenX.dat" where X is the index of
                // the screen in the stack, to ensure the files are uniquely named
                int screenIndex = 0;
                foreach (GameScreen screen in _screens)
                {
                    if (screen.IsSerializable)
                    {
                        string fileName = string.Format("ScreenManager\\Screen{0}.dat", screenIndex);

                        // open up the stream and let the screen serialize whatever state it wants
                        using (IsolatedStorageFileStream stream = storage.CreateFile(fileName))
                        {
                            screen.Serialize(stream);
                        }

                        screenIndex++;
                    }
                }
            }
        }

        public bool DeserializeState()
        {
            // open up isolated storage
            using (IsolatedStorageFile storage = IsolatedStorageFile.GetUserStoreForApplication())
            {
                // see if our saved state directory exists
                if (storage.DirectoryExists("ScreenManager"))
                {
                    try
                    {
                        // see if we have a screen list
                        if (storage.FileExists("ScreenManager\\ScreenList.dat"))
                        {
                            // load the list of screen types
                            using (IsolatedStorageFileStream stream = storage.OpenFile("ScreenManager\\ScreenList.dat", FileMode.Open, FileAccess.Read))
                            {
                                using (BinaryReader reader = new BinaryReader(stream))
                                {
                                    while (reader.BaseStream.Position < reader.BaseStream.Length)
                                    {
                                        // read a line from our file
                                        string line = reader.ReadString();

                                        // if it isn't blank, we can create a screen from it
                                        if (!string.IsNullOrEmpty(line))
                                        {
                                            Type screenType = Type.GetType(line);
                                            GameScreen screen = Activator.CreateInstance(screenType) as GameScreen;
                                            screen.IsActive = false;
                                            AddScreen(screen);
                                        }
                                    }
                                }
                            }
                        }

                        // next we give each screen a chance to deserialize from the disk
                        for (int i = 0; i < _screens.Count; i++)
                        {
                            string filename = string.Format("ScreenManager\\Screen{0}.dat", i);
                            using (IsolatedStorageFileStream stream = storage.OpenFile(filename, FileMode.Open, FileAccess.Read))
                            {
                                _screens[i].Deserialize(stream);
                                _screens[i].IsActive = true;
                            }
                        }

                        if(_screens.Count > 0)
                            return true;
                    }
                    catch (Exception)
                    {
                        // if an exception was thrown while reading, odds are we cannot recover
                        // from the saved state, so we will delete it so the game can correctly
                        // launch.
                        DeleteState(storage);
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Deletes the saved state files from isolated storage.
        /// </summary>
        private void DeleteState(IsolatedStorageFile storage)
        {
            // get all of the files in the directory and delete them
            string[] files = storage.GetFileNames("ScreenManager\\*");
            foreach (string file in files)
            {
                storage.DeleteFile(Path.Combine("ScreenManager", file));
            }
        }
    }
}
